Popular Searches
Popular Course Categories
Popular Courses

Writing Unit Tests Using unittest and pytest Fundamentals

Writing Unit Tests Using unittest and pytest Fundamentals

Python Testing, Performance, and Practical Projects

Writing Unit Tests Using unittest and pytest Fundamentals

Course: Python Training

Topic: Writing Unit Tests Using unittest and pytest Fundamentals

Course Link: Python Training Course at JustAcademy

Demo Registration: Register for a Course Demo

Website: JustAcademy Official Website


1. Introduction to Unit Testing in Python

Unit testing is a software testing technique in which individual units of a program, such as functions, methods, or small classes, are tested independently to verify that they behave as expected. A unit test usually supplies specific inputs, executes a piece of code, and compares the actual result with the expected result.

Unit tests help developers identify defects early, prevent existing functionality from breaking during code changes, and make applications easier to maintain. Python provides the built-in unittest framework, while pytest is a popular third-party testing framework known for its concise syntax, powerful assertions, fixtures, and plugin ecosystem.

Learning Objectives

  • Understand unit testing and its role in software development.
  • Learn the fundamentals of the unittest framework.
  • Create test cases using test classes and assertion methods.
  • Understand the fundamentals of pytest.
  • Write tests using ordinary Python functions and assertions.
  • Organize tests into files and directories.
  • Test successful results, invalid inputs, and raised exceptions.
  • Use setup methods, fixtures, and parameterized test cases.
  • Mock external dependencies when necessary.
  • Run tests, understand test reports, and improve test coverage.

2. What Is Unit Testing?

Unit testing verifies a small, isolated part of an application. For example, if a Python application contains a function that calculates the total price of products, a unit test can check whether the function returns the correct total for different inputs.

2.1 Example Without a Test Framework

def add_numbers(a, b):
    return a + b

result = add_numbers(10, 20)

assert result == 30
print("Test passed")

The assert statement checks whether a condition is true. If it is false, Python raises an AssertionError. This is useful for simple checks, although testing frameworks provide better test discovery, reporting, setup, and execution controls.

2.2 Why Unit Testing Matters

  • Early defect detection: Errors can be identified before software reaches production.
  • Regression prevention: Existing tests help detect when a code change breaks earlier functionality.
  • Safer refactoring: Developers can improve internal code structure while checking that expected behavior remains unchanged.
  • Clear requirements: Tests document expected behavior through executable examples.
  • Faster debugging: Small, focused tests make it easier to locate the source of a problem.
  • Improved maintainability: Reliable tests support ongoing development and collaboration.

2.3 What Makes a Good Unit Test?

  • It tests one behavior or a closely related set of conditions.
  • It produces a repeatable result.
  • It does not depend unnecessarily on network access, real payment systems, or external services.
  • It has a descriptive name.
  • It checks meaningful expected results rather than merely executing code.
  • It runs quickly enough to be used frequently.

3. Understanding the AAA Testing Pattern

A common approach to structuring unit tests is the Arrange–Act–Assert pattern.

Stage Purpose Example
Arrange Prepare inputs and required objects. a = 10 and b = 5
Act Execute the function or method being tested. result = subtract(a, b)
Assert Check whether the actual result matches the expected result. assert result == 5

Example

def multiply(a, b):
    return a * b

def test_multiply():
    # Arrange
    first_number = 4
    second_number = 5

    # Act
    result = multiply(first_number, second_number)

    # Assert
    assert result == 20

The AAA pattern keeps tests easy to read because setup, execution, and verification are clearly separated.


4. Introduction to unittest

unittest is included in Python's standard library, so it does not require a separate package installation. It follows a structure inspired by xUnit testing frameworks and commonly organizes tests into classes that inherit from unittest.TestCase.

4.1 Importing unittest

import unittest

4.2 Creating a Simple unittest Test Case

Consider a module named calculator.py containing a function:

# calculator.py

def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

Create a test file named test_calculator_unittest.py:

import unittest
from calculator import add, subtract

class TestCalculator(unittest.TestCase):

    def test_add(self):
        self.assertEqual(add(10, 5), 15)

    def test_subtract(self):
        self.assertEqual(subtract(10, 5), 5)

if __name__ == "__main__":
    unittest.main()

In this example, TestCalculator inherits from unittest.TestCase. Each method beginning with test_ is discovered as a test method. The assertion methods check whether the results match the expected values.

4.3 Running unittest Tests

Run the test file directly:

python test_calculator_unittest.py

You can also use test discovery from the project directory:

python -m unittest discover

To discover tests in a specific directory, use:

python -m unittest discover -s tests -p "test_*.py"

Test discovery depends on the chosen directory, filename pattern, and project structure. Naming files with the test_ prefix is a common convention.


5. Important unittest Assertion Methods

Assertion methods verify different types of expected behavior. When an assertion fails, the testing framework reports the failure and helps identify the relevant test.

Assertion Purpose Example
assertEqual(a, b) Checks equality. self.assertEqual(add(2, 3), 5)
assertNotEqual(a, b) Checks inequality. self.assertNotEqual(5, 8)
assertTrue(x) Checks that a condition is true. self.assertTrue(10 > 2)
assertFalse(x) Checks that a condition is false. self.assertFalse(2 > 10)
assertIsNone(x) Checks whether a value is None. self.assertIsNone(None)
assertIsNotNone(x) Checks that a value is not None. self.assertIsNotNone("Python")
assertIn(a, b) Checks membership. self.assertIn("py", "python")
assertNotIn(a, b) Checks non-membership. self.assertNotIn("x", "python")
assertIs(a, b) Checks object identity. self.assertIs(value, None)
assertAlmostEqual(a, b) Checks approximate numeric equality. self.assertAlmostEqual(0.1 + 0.2, 0.3)
assertRaises(Error) Checks that code raises a specified exception. with self.assertRaises(ValueError): ...

Example: Testing Multiple Behaviors

import unittest

def is_even(number):
    return number % 2 == 0

class TestEvenNumber(unittest.TestCase):

    def test_even_number(self):
        self.assertTrue(is_even(8))

    def test_odd_number(self):
        self.assertFalse(is_even(7))

    def test_zero_is_even(self):
        self.assertTrue(is_even(0))

if __name__ == "__main__":
    unittest.main()

Each test checks a meaningful behavior, including an ordinary even number, an odd number, and zero.


6. Testing Exceptions with unittest

Functions sometimes reject invalid inputs or raise exceptions when an operation cannot be completed. Tests should verify that these conditions are handled as intended.

6.1 Example: Division by Zero

# calculator.py

def divide(a, b):
    if b == 0:
        raise ValueError("Divisor cannot be zero")
    return a / b

Test the normal result and the invalid input:

import unittest
from calculator import divide

class TestDivision(unittest.TestCase):

    def test_divide_numbers(self):
        self.assertEqual(divide(10, 2), 5)

    def test_division_by_zero(self):
        with self.assertRaises(ValueError):
            divide(10, 0)

if __name__ == "__main__":
    unittest.main()

The assertRaises() context manager passes when the expected exception occurs. If the function returns normally instead, the test fails.

6.2 Checking an Exception Message

def test_division_error_message(self):
    with self.assertRaisesRegex(ValueError, "Divisor cannot be zero"):
        divide(10, 0)

Use exception-message checks when the message is part of the behavior your application promises. Avoid making tests unnecessarily dependent on incidental wording.


7. Setup and Teardown in unittest

Test cases sometimes require common preparation, such as creating an object, initializing test data, or setting up a temporary resource. The unittest framework provides setup and cleanup methods for this purpose.

Method When it runs Purpose
setUp() Before each test method. Creates fresh test state for an individual test.
tearDown() After each test method, if setup was entered. Performs cleanup for that test.
setUpClass() Once before the test methods in a test class. Prepares resources shared by the class.
tearDownClass() Once after the test methods in a test class. Cleans up class-level resources.

Example

import unittest

class ShoppingCart:
    def __init__(self):
        self.items = []

    def add_item(self, item):
        self.items.append(item)

    def item_count(self):
        return len(self.items)

class TestShoppingCart(unittest.TestCase):

    def setUp(self):
        self.cart = ShoppingCart()

    def test_empty_cart(self):
        self.assertEqual(self.cart.item_count(), 0)

    def test_add_item(self):
        self.cart.add_item("Book")
        self.assertEqual(self.cart.item_count(), 1)

if __name__ == "__main__":
    unittest.main()

Using setUp() creates a fresh cart for each test. This prevents changes in one test from unexpectedly affecting another.


8. Introduction to pytest

pytest is a third-party Python testing framework designed to make tests concise and easy to extend. Unlike traditional unittest test cases, pytest can use ordinary functions with Python's built-in assert statement.

8.1 Installing pytest

python -m pip install pytest

Run the installation command in the same Python environment that you use to execute your application and tests.

8.2 Writing a Basic pytest Test

Using the same calculator.py module:

# test_calculator.py

from calculator import add, subtract

def test_add():
    assert add(10, 5) == 15

def test_subtract():
    assert subtract(10, 5) == 5

These tests are ordinary functions. The names begin with test_, and each uses assert to check the expected result.

8.3 Running pytest

pytest

To display more detailed test names and results:

pytest -v

To run tests from a specific directory:

pytest tests/

To run one test file:

pytest tests/test_calculator.py

To run a particular test function:

pytest tests/test_calculator.py::test_add

9. Understanding pytest Assertions

Pytest uses Python's normal assert statement. When an assertion fails, pytest typically displays the values and expressions involved, which makes debugging easier.

9.1 Testing Numeric Results

def calculate_total(price, quantity):
    return price * quantity

def test_calculate_total():
    assert calculate_total(100, 3) == 300

9.2 Testing Strings and Collections

def get_supported_languages():
    return ["Python", "JavaScript", "SQL"]

def test_supported_languages():
    languages = get_supported_languages()

    assert "Python" in languages
    assert len(languages) == 3
    assert languages[0] == "Python"

9.3 Testing Boolean Results

def is_valid_username(username):
    return isinstance(username, str) and len(username) >= 3

def test_valid_username():
    assert is_valid_username("alex")

def test_short_username():
    assert not is_valid_username("ab")

9.4 Testing Approximate Floating-Point Values

Floating-point arithmetic can produce small precision differences. Pytest provides pytest.approx() for checking approximate numeric results.

import pytest

def calculate_percentage(value, total):
    return value / total

def test_calculate_percentage():
    result = calculate_percentage(1, 10)
    assert result == pytest.approx(0.1)

Use approximate comparison when a small floating-point difference is acceptable. For financial calculations, consider whether decimal arithmetic is more suitable than binary floating-point numbers.


10. Testing Exceptions with pytest

Pytest provides pytest.raises() to check that a function raises an expected exception.

import pytest

def validate_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative")
    return True

def test_valid_age():
    assert validate_age(25) is True

def test_negative_age():
    with pytest.raises(ValueError):
        validate_age(-1)

10.1 Inspecting Exception Details

def test_negative_age_message():
    with pytest.raises(ValueError, match="Age cannot be negative"):
        validate_age(-1)

The match argument checks the exception message using a regular expression. Use it when the message is an important part of the expected behavior.


11. Parameterized Testing in pytest

Parameterized testing lets one test function run with several input and expected-output combinations. It reduces repeated code while ensuring that different cases are checked independently.

11.1 Example with pytest.mark.parametrize

import pytest

def add(a, b):
    return a + b

@pytest.mark.parametrize(
    "a, b, expected",
    [
        (2, 3, 5),
        (10, 5, 15),
        (-2, 2, 0),
        (0, 0, 0),
    ]
)
def test_add(a, b, expected):
    assert add(a, b) == expected

Pytest executes the test function for every parameter set. If one case fails, the report identifies the failing combination.

11.2 Testing Different Input Types

import pytest

@pytest.mark.parametrize(
    "value, expected",
    [
        (2, True),
        (3, False),
        (0, True),
        (-4, True),
    ]
)
def test_is_even(value, expected):
    assert value % 2 == 0 is expected

For clearer operator behavior, the test can instead use assert (value % 2 == 0) is expected. This makes the intended comparison explicit.


12. Fixtures in pytest

A fixture prepares reusable data or resources for tests. Fixtures can create objects, supply sample records, provide temporary paths, or initialize dependencies needed by a test.

12.1 Creating a Simple Fixture

import pytest

@pytest.fixture
def sample_numbers():
    return [10, 20, 30, 40]

def test_sum_of_numbers(sample_numbers):
    assert sum(sample_numbers) == 100

def test_number_count(sample_numbers):
    assert len(sample_numbers) == 4

When a test requests sample_numbers as an argument, pytest runs the fixture and passes its return value to the test.

12.2 Fixtures for Objects

import pytest

class ShoppingCart:
    def __init__(self):
        self.items = []

    def add_item(self, item):
        self.items.append(item)

    def total_items(self):
        return len(self.items)

@pytest.fixture
def cart():
    return ShoppingCart()

def test_new_cart_is_empty(cart):
    assert cart.total_items() == 0

def test_cart_with_one_item(cart):
    cart.add_item("Notebook")
    assert cart.total_items() == 1

12.3 Fixture Scope

Scope Typical lifecycle Common use
function Once per test function; default scope. Fresh test data and objects.
class Shared within a test class. Resources reused by methods in one class.
module Shared within a module. Expensive setup for tests in one file.
session Shared across a test session. Expensive, safe-to-share resources.

Example of a module-scoped fixture:

import pytest

@pytest.fixture(scope="module")
def configuration():
    return {
        "environment": "testing",
        "debug": True
    }

def test_environment(configuration):
    assert configuration["environment"] == "testing"

Choose the narrowest scope that makes sense. Shared mutable state can cause tests to influence one another if it is not managed carefully.


13. Using Setup and Cleanup in pytest Fixtures

Fixtures can use yield to separate resource setup from cleanup. The code before yield prepares the resource, and the code after it performs cleanup after the dependent tests finish.

import pytest

@pytest.fixture
def temporary_resource():
    resource = {"active": True}

    yield resource

    resource["active"] = False

def test_resource_is_active(temporary_resource):
    assert temporary_resource["active"] is True

This simplified example demonstrates fixture teardown. In real applications, cleanup may close a connection, remove a temporary file, stop a service, or release another resource.

Example with a Temporary File

def test_write_to_temporary_file(tmp_path):
    file_path = tmp_path / "message.txt"
    file_path.write_text("Hello, Python!", encoding="utf-8")

    assert file_path.exists()
    assert file_path.read_text(encoding="utf-8") == "Hello, Python!"

The tmp_path fixture is provided by pytest. It supplies a temporary directory for the test, helping isolate file operations from real project data.


14. Organizing Test Files and Project Structure

Organized test files make projects easier to navigate and allow test runners to discover tests reliably.

14.1 Example Project Structure

python_project/
    calculator.py
    user_service.py
    requirements.txt
    tests/
        test_calculator.py
        test_user_service.py

14.2 Naming Conventions

  • Use filenames such as test_calculator.py.
  • Use function names such as test_add_positive_numbers().
  • For unittest, group related methods in classes inheriting from unittest.TestCase.
  • Keep tests near the functionality they verify or inside a dedicated tests directory.
  • Use clear names that explain the scenario and expected behavior.

14.3 Example Test Module

# user_service.py

def normalize_username(username):
    return username.strip().lower()
# tests/test_user_service.py

from user_service import normalize_username

def test_normalize_username():
    assert normalize_username("  Alice  ") == "alice"

def test_normalize_username_lowercase():
    assert normalize_username("bob") == "bob"

When running tests from the project root, make sure the source modules are importable. Depending on the package structure, you may need a package layout or a project installation in the active environment.


15. Running Selected Tests and Understanding Test Reports

15.1 Useful pytest Commands

Command Purpose
pytest Runs tests discovered from the current directory and configured paths.
pytest -v Shows individual test names and results.
pytest -q Displays a more concise report.
pytest tests/test_calculator.py Runs tests from one file.
pytest -k "add" Runs tests whose names match the expression.
pytest -x Stops after the first failure.
pytest --maxfail=2 Stops after two failures.
pytest --collect-only Lists tests that would be collected without executing them.

15.2 Understanding Common Outcomes

  • Passed: The test completed and all assertions succeeded.
  • Failed: The test ran, but an assertion did not match the expected result.
  • Error: An unexpected problem occurred while setting up or executing the test.
  • Skipped: The test was intentionally not executed, often because a condition was not met.
  • Expected failure: A test was marked as expected to fail; the exact reporting behavior depends on the framework and configuration.

15.3 Debugging a Failed Test

  1. Read the failing test name and the assertion or traceback.
  2. Check the input values supplied to the function.
  3. Compare the actual output with the expected output.
  4. Reproduce the issue with a small example if necessary.
  5. Correct the implementation or fix an incorrect test expectation.
  6. Run the test again and then run the broader test suite.

16. Mocking Dependencies in Unit Tests

Some functions rely on external services, such as APIs, email providers, payment gateways, databases, or system clocks. A unit test should usually avoid making unnecessary real external calls. Mocking replaces a dependency with a controlled test double.

Python's standard library provides unittest.mock, which can be used with both unittest and pytest.

16.1 Example: Mocking an API Request

# weather_service.py

import requests

def get_temperature(city):
    response = requests.get(
        f"https://example.com/weather?city={city}",
        timeout=5
    )
    response.raise_for_status()
    data = response.json()
    return data["temperature"]

Instead of contacting a real endpoint, a unit test can mock the HTTP request:

# test_weather_service.py

from unittest.mock import Mock, patch
from weather_service import get_temperature

@patch("weather_service.requests.get")
def test_get_temperature(mock_get):
    mock_response = Mock()
    mock_response.json.return_value = {"temperature": 28}
    mock_response.raise_for_status.return_value = None

    mock_get.return_value = mock_response

    result = get_temperature("Mumbai")

    assert result == 28
    mock_get.assert_called_once_with(
        "https://example.com/weather?city=Mumbai",
        timeout=5
    )

The patch target is weather_service.requests.get because that is where the function looks up the dependency. Mock the name used by the module under test rather than blindly patching a different reference.

16.2 When to Use Mocking

  • To avoid real network requests.
  • To prevent tests from sending real emails or messages.
  • To simulate failures such as timeouts or unavailable services.
  • To verify that a dependency was called with expected arguments.
  • To isolate business logic from infrastructure.

Do not mock every internal function automatically. Excessive mocking can make tests tightly coupled to implementation details and may fail to verify the behavior that matters to users.


17. Comparing unittest and pytest

Feature unittest pytest
Installation Included in Python's standard library. Requires installation as a third-party package.
Typical test structure Classes inheriting from unittest.TestCase. Ordinary functions or unittest-style classes.
Assertions Uses methods such as assertEqual() and assertRaises(). Uses Python assert and helpers such as pytest.raises().
Test setup Methods such as setUp() and tearDown(). Fixtures with configurable scopes and cleanup.
Parameterized tests Can use subtests or other techniques. Provides pytest.mark.parametrize.
Mocking Works with unittest.mock. Also works with unittest.mock and pytest plugins.
Reporting Provides standard unittest test-runner output. Offers concise, verbose, filtering, and plugin-based reporting options.
Best fit Projects needing a built-in framework or established unittest conventions. Projects benefiting from concise tests, fixtures, and an extensible ecosystem.

Both frameworks are valid choices. Pytest can run many unittest test cases, so teams can often adopt pytest gradually without rewriting every existing test.


18. Code Coverage and Test Quality

Code coverage measures how much of the code is executed during tests. It can help identify code paths that have not been exercised, but high coverage alone does not guarantee that tests verify meaningful behavior.

18.1 Installing pytest-cov

python -m pip install pytest-cov

18.2 Measuring Coverage

pytest --cov=calculator --cov-report=term-missing

This command measures coverage for the specified module and prints a report that can help identify untested lines. Ensure the module name matches your project.

18.3 Improving Test Quality

  • Test normal, boundary, and invalid inputs.
  • Verify return values and important side effects.
  • Check exception behavior where it is part of the function's contract.
  • Test branches such as empty lists, zero values, and missing records.
  • Keep tests independent and repeatable.
  • Do not write tests that only repeat the implementation without checking requirements.
  • Review uncovered branches and decide which deserve additional tests.

19. Common Unit Testing Mistakes and Solutions

Mistake Why it causes problems Recommended solution
Incorrect test naming The test runner may not discover the test. Use recognized names such as test_example.py and test_example().
Testing only the happy path Boundary cases and invalid inputs may remain untested. Add tests for empty, negative, invalid, and boundary values where relevant.
Sharing mutable state One test can affect the result of another. Create fresh objects or data through setup methods or fixtures.
Making real external calls Tests can become slow, unreliable, or costly. Mock external dependencies for unit tests.
Overusing mocks Tests may verify implementation details instead of behavior. Mock only meaningful boundaries and dependencies.
Using unclear names Failures are harder to understand. Name tests after the behavior and condition being checked.
Ignoring failing tests Regressions can remain undetected. Investigate failures and rerun the relevant suite after fixing them.
Assuming coverage equals quality Executed lines may not be meaningfully verified. Check assertions, edge cases, and requirements in addition to coverage.
Writing dependent tests Execution order can change outcomes. Make every test establish its own prerequisites.

20. Complete Mini Project: Testing a Calculator Module

Project Objective

Create a small calculator module and test addition, subtraction, multiplication, division, invalid division, and parameterized inputs. This project demonstrates the fundamentals of both unittest and pytest.

Step 1: Create calculator.py

# calculator.py

def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

def multiply(a, b):
    return a * b

def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

Step 2: Create test_calculator.py Using pytest

# test_calculator.py

import pytest
from calculator import add, subtract, multiply, divide

def test_add():
    assert add(2, 3) == 5

def test_subtract():
    assert subtract(8, 3) == 5

def test_multiply():
    assert multiply(4, 3) == 12

def test_divide():
    assert divide(10, 2) == 5

def test_divide_by_zero():
    with pytest.raises(ValueError, match="Cannot divide by zero"):
        divide(10, 0)

@pytest.mark.parametrize(
    "a, b, expected",
    [
        (1, 2, 3),
        (0, 0, 0),
        (-2, 2, 0),
        (10, -5, 5),
    ]
)
def test_add_multiple_cases(a, b, expected):
    assert add(a, b) == expected

Step 3: Run the Tests

pytest -v

Review the report to confirm which tests passed and whether any failures occurred.

Step 4: Create test_calculator_unittest.py

# test_calculator_unittest.py

import unittest
from calculator import add, subtract, multiply, divide

class TestCalculator(unittest.TestCase):

    def test_add(self):
        self.assertEqual(add(2, 3), 5)

    def test_subtract(self):
        self.assertEqual(subtract(8, 3), 5)

    def test_multiply(self):
        self.assertEqual(multiply(4, 3), 12)

    def test_divide(self):
        self.assertEqual(divide(10, 2), 5)

    def test_divide_by_zero(self):
        with self.assertRaisesRegex(
            ValueError,
            "Cannot divide by zero"
        ):
            divide(10, 0)

if __name__ == "__main__":
    unittest.main()

Step 5: Run unittest

python -m unittest -v test_calculator_unittest.py

Step 6: Extend the Project

  • Add tests for negative numbers and decimal inputs.
  • Decide how the calculator should handle invalid input types and test that behavior.
  • Add a power function and write tests for zero and negative exponents.
  • Introduce a function that calculates the average of a list and test empty-list behavior.
  • Run the test suite after every implementation change.
  • Measure code coverage and identify important untested branches.

Expected Learning Outcomes

  • Build a small Python module with defined behavior.
  • Write and run tests using two testing frameworks.
  • Test normal results and exceptions.
  • Use parameterized tests to reduce repetitive code.
  • Interpret test reports and improve coverage.

21. Practice Exercises

  1. Write a function that returns the square of a number and create tests for positive, negative, and zero inputs.
  2. Create a function that checks whether a number is prime and test prime and non-prime values.
  3. Write a function that reverses a string and test empty strings and strings with spaces.
  4. Create a function that validates an email-like input according to simple rules you define, then test valid and invalid examples.
  5. Write a function that calculates a discount and test boundary conditions such as a zero discount and a full discount.
  6. Test a function that returns the largest value in a list, including the behavior for an empty list.
  7. Create a function that converts Celsius to Fahrenheit and test known reference values.
  8. Use unittest.TestCase to test a simple shopping cart.
  9. Rewrite selected tests using pytest and compare readability.
  10. Use pytest.mark.parametrize to test multiple input and expected-output pairs.
  11. Create a fixture that supplies sample customer records to several tests.
  12. Use pytest.raises() to test an expected exception.
  13. Mock a function that sends an email so that no real message is sent during testing.
  14. Organize a small project into source and test directories and run test discovery.
  15. Generate a test coverage report and add tests for important missing branches.

22. Interview Questions and Answers

Q1. What is unit testing?

Answer: Unit testing checks individual functions, methods, or small components to verify that they behave as expected.

Q2. What is the difference between unit testing and integration testing?

Answer: Unit testing focuses on an isolated component, while integration testing checks whether multiple components or services work correctly together.

Q3. What is unittest?

Answer: unittest is Python's built-in testing framework. It commonly organizes tests into classes that inherit from unittest.TestCase.

Q4. What is pytest?

Answer: Pytest is a third-party testing framework that supports simple test functions, Python assertions, fixtures, parameterization, and extensible reporting.

Q5. Does unittest require installation?

Answer: No. It is included in Python's standard library.

Q6. How do you install pytest?

Answer: Run python -m pip install pytest in the intended Python environment.

Q7. What is a test case?

Answer: A test case is a defined check of a particular behavior under specific conditions, including its inputs and expected outcome.

Q8. What is an assertion?

Answer: An assertion checks whether an expression or result matches an expected condition. A failed assertion causes the test to fail.

Q9. What is the AAA pattern?

Answer: Arrange prepares the test data, Act executes the behavior, and Assert verifies the result.

Q10. What is the purpose of setUp() in unittest?

Answer: It prepares the test environment before each test method, commonly by creating fresh objects or initializing test data.

Q11. What is a pytest fixture?

Answer: A fixture supplies reusable test data or resources and can manage setup and cleanup for tests.

Q12. What is parameterized testing?

Answer: It runs the same test logic against multiple input and expected-result combinations.

Q13. How do you test an exception in unittest?

Answer: Use a context manager such as with self.assertRaises(ValueError): around the operation expected to raise the exception.

Q14. How do you test an exception in pytest?

Answer: Use with pytest.raises(ValueError): around the operation expected to raise the exception.

Q15. What is mocking?

Answer: Mocking replaces a dependency with a controlled test double so that a component can be tested without relying on the real dependency.

Q16. Why should unit tests be independent?

Answer: Independent tests produce reliable results regardless of execution order and are easier to debug.

Q17. How do you run a specific pytest test?

Answer: Use a command such as pytest tests/test_calculator.py::test_add.

Q18. What is code coverage?

Answer: Code coverage measures which parts of a codebase are executed during testing. It helps identify gaps but does not guarantee that the tests are effective.

Q19. Can pytest run unittest test cases?

Answer: Yes. Pytest supports many existing unittest-style test cases, allowing teams to use pytest as a test runner without necessarily rewriting all tests.

Q20. What is a regression test?

Answer: A regression test checks existing behavior to help ensure that a code change has not introduced a new defect.


23. Quick Revision Checklist

  • Understand the purpose of unit testing and the AAA pattern.
  • Know that unittest is part of Python's standard library.
  • Use unittest.TestCase and methods beginning with test_.
  • Understand assertion methods such as assertEqual() and assertRaises().
  • Use setUp() and cleanup methods where appropriate.
  • Install and run pytest using python -m pip install pytest and pytest.
  • Write pytest functions with descriptive names and ordinary assert statements.
  • Use pytest.raises() to verify exceptions.
  • Use pytest.mark.parametrize for multiple input cases.
  • Use fixtures to supply reusable data and manage resources.
  • Mock external dependencies when isolation is needed.
  • Organize test files so that test discovery works correctly.
  • Interpret passed, failed, skipped, and error results.
  • Use coverage reports to identify untested code paths.
  • Keep tests meaningful, repeatable, independent, and maintainable.

24. Conclusion

Unit testing is an essential part of reliable Python development. The built-in unittest framework provides a structured approach based on test classes and assertion methods, while pytest offers concise test functions, flexible fixtures, parameterized testing, and helpful reporting.

By testing normal inputs, edge cases, invalid inputs, exceptions, and external dependencies, developers can identify defects earlier and make changes with greater confidence. Consistent naming, independent tests, meaningful assertions, and regular test execution help maintain software quality as projects grow.

Continue learning through the Python Training Course at JustAcademy. You can register for a course demo or visit the JustAcademy official website for more learning resources.

whatsapp